package cn.tedu.lbs.basic.web.service;

import cn.tedu.lbs.basic.web.repository.AdminRepository;
import cn.tedu.lbs.basic.web.repository.BusinessRepository;
import cn.tedu.lbs.basic.web.repository.StoreESRepository;
import cn.tedu.lbs.basic.web.repository.StoreRepository;
import cn.tedu.lbs.basic.web.source.BasiceOutputSource;
import cn.tedu.lbs.basic.web.utils.AuthenticationContextUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tarena.lbs.attach.api.AttachApi;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.passport.enums.Roles;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.pojo.attach.param.PicUpdateParam;
import com.tarena.lbs.pojo.basic.entity.StoreSearchEntity;
import com.tarena.lbs.pojo.basic.event.LocationEvent;
import com.tarena.lbs.pojo.basic.param.StoreParam;
import com.tarena.lbs.pojo.basic.param.UserLocationParam;
import com.tarena.lbs.pojo.basic.po.AdminPO;
import com.tarena.lbs.pojo.basic.po.BusinessPO;
import com.tarena.lbs.pojo.basic.po.StorePO;
import com.tarena.lbs.pojo.basic.query.AreaStoreQuery;
import com.tarena.lbs.pojo.basic.query.StoreQuery;
import com.tarena.lbs.pojo.basic.vo.StoreVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class StoreService {
    @Autowired
    private StoreRepository storeRepository;
    @Autowired
    private AdminRepository adminRepository;
    @Autowired
    private BusinessRepository businessRepository;

    public PageResult<StoreVO> storeList(StoreQuery storeQuery) throws BusinessException {
        //1.根据当前登录用户的角色 查询businessId ADMIN角色不用查询 SHOP 需要查询
        Integer businessId=checkRoleAndGetBusinessId();
        //2.query中根据第一步查询结果 多封装一个数据businessId 拼接where查询
        storeQuery.setBusinessId(businessId);
        //2.1准备一个分页result 填补属性 唯一需要持久层参与的就是list查询
        PageResult<StoreVO> voPage=
                new PageResult<>(storeQuery.getPageNo(), storeQuery.getPageSize());
        voPage.setTotal(100l);
        QueryWrapper<StorePO> queryWrapper=new QueryWrapper<>();
        if (storeQuery.getBusinessId()!=null){
            queryWrapper.eq("business_id",storeQuery.getBusinessId());
        }
        List<StorePO> pos = storeRepository.list(queryWrapper);
        List<StoreVO> vos=null;
        if (CollectionUtils.isNotEmpty(pos)){
            vos=pos.stream().map(po->{
                StoreVO vo=new StoreVO();
                BeanUtils.copyProperties(po,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }

    private Integer checkRoleAndGetBusinessId() throws BusinessException {
        Integer businessId=null;
        //1.拿到认证对象
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        //2.断言 非空
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        //3.判断是否是SHOP 是SHOP 使用user的id 查询admin表格获取businessId关联的商家id返回
        if (userPrinciple.getRole()== Roles.SHOP){
            Integer adminId = userPrinciple.getId();
            //查询lbs_admin表格 adminPO 获取businessId
            AdminPO po = adminRepository.getById(adminId);
            Asserts.isTrue(po==null,new BusinessException("-2","用户信息解析失败"));
            businessId=po.getBusinessId();
            log.info("查询商家账号的关联商家id;入参:{},出参:{}",adminId,businessId);
        }
        return businessId;
    }

    public void storeAdd(StoreParam storeParam) throws BusinessException {
        //1.验证角色
        checkRole(Roles.SHOP);
        //2.验证入参中的businessId有没有对应合法的商家
        checkBusiness(storeParam.getBusinessId());
        //3.组织数据新增 到db和es
        Integer id=save(storeParam);
        //4.绑定图片 图片id 店铺id 图片业务类型 店铺logo type=300 店铺图片 type=400
        bindPictures(storeParam,id);
    }
    @Autowired
    private StoreESRepository storeESRepository;
    private Integer save(StoreParam param){
        //1.将param拷贝属性po 补充 缺少内容
        StorePO poParam = new StorePO();
        BeanUtils.copyProperties(param,poParam);
        //1.1补充数据 地理位置 入参longitude latitude 字段storeLongitude storeLatitude
        poParam.setStoreLatitude(param.getLatitude());
        poParam.setStoreLongitude(param.getLongitude());
        //1.2转化门店图片 入参 [1,5,10] 字段 "1,5,10"
        List<String> storeImagesIds = param.getStoreImagesIds();
        String join = String.join(",", storeImagesIds);
        log.info("拼接完整的一个 图片字符串 使用,分割:{}",join);
        poParam.setStoreImagesId(join);
        //1.3补充创建和修改时间
        poParam.setCreateTime(new Date());
        poParam.setUpdateTime(new Date());
        //2.调用仓储层新增po
        storeRepository.save(poParam);
        //3.封装文档对象 存储到es 使用po封装entity 调用注入的ESRepository写入es
        //准备一个es的实体类 entity
        StoreSearchEntity entity=new StoreSearchEntity();
        entity.setId(poParam.getId());
        entity.setStoreName(poParam.getStoreName());
        entity.setBusinessId(poParam.getBusinessId());
        //在es中 保存地理位置 有2种方式 一种使用GeoPoint类型 一种就是使用精度维度拼接字符串
        //lat,lon
        entity.setLocation(poParam.getStoreLatitude()+","+poParam.getStoreLongitude());
        storeESRepository.save(entity);
        return poParam.getId();

    }

    private void checkBusiness(Integer businessId) throws BusinessException {
        //使用businessId查询BusinessPO 断言 非空
        BusinessPO po = businessRepository.getById(businessId);
        Asserts.isTrue(po==null,new BusinessException("-2","商家不存在"));
    }

    private void checkRole(Roles role) throws BusinessException {
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        Asserts.isTrue(userPrinciple.getRole()!=role,new BusinessException("-2","业务权限不满足"));
    }
    @DubboReference
    private AttachApi attachApi;
    private void bindPictures(StoreParam storeParam, Integer bizId) {
        List<PicUpdateParam> params=new ArrayList<>();
        //1.绑定图片的入参有2种类型 businessType=300 店铺logo 一张图片
        Integer logoId = Integer.valueOf(storeParam.getStoreLogo());
        PicUpdateParam logoParam=new PicUpdateParam();
        logoParam.setId(logoId);
        logoParam.setBusinessType(300);
        logoParam.setBusinessId(bizId);
        params.add(logoParam);
        //2.绑定图片第二种类型 店铺门店图片 N张 businessType=400
        //封装数据过程 就是将入参中的storeImageIds 映射 List<PicUpdateParam>
        List<PicUpdateParam> imgParams=storeParam.getStoreImagesIds().stream().map(imgId->{
            PicUpdateParam imgParam=new PicUpdateParam();
            imgParam.setBusinessId(bizId);
            imgParam.setBusinessType(400);
            imgParam.setId(Integer.valueOf(imgId));
            return imgParam;
        }).collect(Collectors.toList());
        params.addAll(imgParams);
        attachApi.batchUpdateBusiness(params);
    }

    public PageResult<StoreVO> storeByCity(AreaStoreQuery areaStoreQuery) throws BusinessException {
        PageResult<StoreVO> voPage = new PageResult<>(areaStoreQuery);
        voPage.setTotal(100L);
        //1.获取用户id
        Integer adminId=getUserId();
        //2.查询商家id lbs_admin
        Integer businessId=checkAndGetBusinessId(adminId);
        //3.调用数据仓储层查询 列表
        List<StorePO> pos=getCityStores(businessId,areaStoreQuery.getCityIdList());
        //4.po转化vo封装返回
        List<StoreVO> vos=null;
        if (CollectionUtils.isNotEmpty(pos)){
            vos=pos.stream().map(po->{
                StoreVO vo=new StoreVO();
                BeanUtils.copyProperties(po,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }

    private List<StorePO> getCityStores(Integer businessId, List<Long> cityIdList) {
        //调用数据层 做queryWrapper配置
        QueryWrapper<StorePO> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("business_id",businessId).
                and(
                        wq->wq.in("province_id",cityIdList).
                                or().in("city_id",cityIdList).
                                or().in("area_id",cityIdList));
        return storeRepository.list(queryWrapper);
    }

    private Integer checkAndGetBusinessId(Integer adminId) throws BusinessException {
        AdminPO po = adminRepository.getById(adminId);
        Asserts.isTrue(po==null,new BusinessException("-2","商家账号不存在"));
        return po.getBusinessId();
    }

    private Integer getUserId() throws BusinessException {
        //读取 认证 传递的userPrinciple 线程和过滤器线程相同
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        return userPrinciple.getId();
    }
    @Autowired
    private BasiceOutputSource outputSource;
    public void location(UserLocationParam param) throws BusinessException {
        //1.拿到userId 表明定位上报的用户身份
        Integer userId = getUserId();
        //2.调用数据层 使用地理位置 查询附近的店铺 null list
        List<StoreSearchEntity> entitis=getNearStores(param);
        log.info("当前用户定位上报:{},查询到的店铺有:{}",param,entitis);
        //3.店铺非空 封装消息事件 通过spring cloud stream 输出源发送消息
        //消息数据的封装 应该保证 精简准确 准确是消费者拿到数据不能缺失 精简 减少消息中携带臃肿
        MessageChannel channel = outputSource.getStoreLocationOutput();
        //将定位到的entities店铺实体 做循环 每循环一次 组织一个消息发送
        try{
            for (StoreSearchEntity entity : entitis) {
                LocationEvent event=new LocationEvent();
                event.setUserId(userId);
                event.setStoreId(entity.getId());
                channel.send(MessageBuilder.withPayload(event).build());
                log.info("basic定位店铺消息发送成功:{}",event);
            }
        }catch (Exception e){
            log.error("发送消息失败",e);
        }
    }
    @Autowired
    private RestHighLevelClient client;
    private List<StoreSearchEntity> getNearStores(UserLocationParam param) {
        //query只包含一个geoDistance size(5)
        //1.查询请求
        SearchRequest request=new SearchRequest("lbs_store");
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //2.封装参数
        sourceBuilder.size(5);
        GeoDistanceQueryBuilder query = QueryBuilders.geoDistanceQuery("location");
        //query中心
        query.point(Double.valueOf(param.getLatitude()),Double.valueOf(param.getLongitude()));
        //query距离 5KM|10KM|20KM
        query.distance(20d, DistanceUnit.KILOMETERS);
        sourceBuilder.query(query);
        request.source(sourceBuilder);
        //3.发起请求 解析响应
        List<StoreSearchEntity> entities=null;
        try{
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            SearchHit[] docs = response.getHits().getHits();
            if (docs!=null&&docs.length>0){
                entities= Arrays.stream(docs).map(doc->{
                    return JSON.parseObject(doc.getSourceAsString(),StoreSearchEntity.class);
                }).collect(Collectors.toList());
            }
        }catch (Exception e){
            log.error("查询附近的店铺失败",e);
        }
        return entities;
    }
}
